The way we write JavaScript can always be improved.
In this article, we’ll look at how to improve our code styles by improving the basic syntax we use in our code.
Error Handling
Error handling should be kept as simple as possible. If we encounter an error when running a function, we should throw an error.
For instance, we can throw an error if we encounter an invalid value as follows:
const greet = (name) => {
if (typeof name !== 'string') {
throw new Error('name not provided');
}
return `hi ${name}`;
}
In the code above, we check if name
is a string. If it isn’t, then we throw an error.
Otherwise, we return the string with the greeting.
This is better than the alternative, which is returning error values:
const greet = (name) => {
if (typeof name !== 'string') {
return '';
}
return `hi ${name}`;
}
In the code above, we returned an empty string instead of throwing an error, so we hid the error from the developer using the function and the user.
It’s very easy to overlook error values since they aren’t immediately obvious. Unlike errors, which will show in the console immediately, we don’t see error values until we’re debugging.
This makes debugging harder since error values don’t show in the console.
Then if we need to handle errors, we can use the try...catch
block as follows:
const greet = (name) => {
if (typeof name !== 'string') {
throw new Error('name not provided');
}
return `hi ${name}`;
}
try {
greet();
} catch (ex) {
console.log(ex);
}
In the code above, we have the following try...catch
block:
try {
greet();
} catch (ex) {
console.log(ex);
}
Then since we didn’t pass in any argument into the greet
function when we call it, we get an error in the console from the catch
block.
To handle it better, we can choose to display an error message or something like that.
For async code, we should use promises as much as possible. Promises are rejected if errors are encountered, and like throwing errors in synchronous code, they also show up in the console.
For instance, if we have a promise that’s rejected as follows:
Promise.reject('error');
We can call catch
on it to catch the error and handle it as follows:
Promise.reject('error')
.catch(err => console.log(err));
In the code above, we called the catch
method and in the catch
method call, we passed in a callback to log the error.
Then we can get see errors and handle them in async code easily.
With the async
and await
syntax, we can catch errors from rejected promises as follows:
(async () => {
try {
await Promise.reject('error');
} catch (err) {
console.log(err);
}
})();
As we can see, we just use try...catch
like we did with synchronous code if we use the async
and await
syntax.
Comparison
===
and !==
are always better than ==
and !=
.
This is because ===
and !==
do not coerce the data types of their operands before doing the comparison.
On the other hand, ==
and !=
do data type coercion on their operands before doing any comparison.
By skipping typing one character, we introduced lots of headaches for ourselves because of the automatic type coercion.
Therefore, we should always just type the extra =
sign and reduce the chances of bugs significantly.
To keep conditional expressions simple, we should use shortcuts as much as possible.
For instance, the following is bad:
if (condition === true) {
// ...
}
This is bad because we don’t need to compare against true
to check if something is true
.
Instead, we should write:
if (condition) {
// ...
}
Then we check if condition
is truthy or not, which includes the value true
.
If we use ternary expressions, we shouldn’t nest them. For instance, the following is good:
const foo = cond ? 'foo' : 'bar';
This is because we only have one condition cond
and return 'foo'
is cond
is true
and 'bar'
otherwise.
However, we shouldn’t nest multiple ternary expressions together. So the following is bad:
const foo = cond1 ? cond2 ? 'foo' : 'bar' : 'baz';
Nesting makes the expression hard to understand. We’ve to put in the parentheses in our brains to compute the value in our heads so that we can understand what it’s doing.
Conclusion
Error handling is nest done by throwing errors and then handling them by catching them.
For async code, we can reject promises and then call catch
or use the catch
block to handle errors.
For conditionals, we should use the shortest way if we can. We also shouldn’t nest ternary expressions.
To do comparisons, we should use ===
and !==
for comparisons so that we don’t have to worry about type coercions.